﻿using System;
using System.Collections.Generic;
using System.Transactions;
using System.Linq;

namespace ADDDF.Infrastructure
{
    public class UnitOfWork : IUnitOfWork
    {
        #region Fields

        private List<Operation> _operations;

        #endregion

        #region Constructors

        public UnitOfWork()
        {
            _operations = new List<Operation>();
        }

        #endregion

        #region Operation

        /// <summary>
        /// Provides a snapshot of an entity and the repository reference it belongs to.
        /// </summary>
        private sealed class Operation
        {
            /// <summary>
            /// Gets or sets the entity.
            /// </summary>
            /// <value>The entity.</value>
            public IAggregateRoot Entity { get; set; }

            /// <summary>
            /// Gets or sets the process date.
            /// </summary>
            /// <value>The process date.</value>
            public DateTime ProcessDate { get; set; }

            /// <summary>
            /// Gets or sets the repository.
            /// </summary>
            /// <value>The repository.</value>
            public IRepository Repository { get; set; }

            /// <summary>
            /// Gets or sets the type of operation.
            /// </summary>
            /// <value>The type of operation.</value>
            public TransactionType Type { get; set; }
        }

        #endregion

        #region IUnitOfWork Members

        /// <summary>
        /// Registers an <see cref="IEntity" /> instance to be added through this <see cref="UnitOfWork" />.
        /// </summary>
        /// <param name="entity">The <see cref="IEntity" />.</param>
        /// <param name="repository">The <see cref="IUnitOfWorkRepository" /> participating in the transaction.</param>
        public void RegisterAdded(
            IAggregateRoot entity, 
            IRepository repository)
        {
            _operations.Add(
                new Operation
                {
                    Entity = entity,
                    ProcessDate = DateTime.Now,
                    Repository = repository,
                    Type = TransactionType.Insert
                });

        }

        /// <summary>
        /// Registers an <see cref="IEntity" /> instance to be changed through this <see cref="UnitOfWork" />.
        /// </summary>
        /// <param name="entity">The <see cref="IEntity" />.</param>
        /// <param name="repository">The <see cref="IUnitOfWorkRepository" /> participating in the transaction.</param>
        public void RegisterChanged(
            IAggregateRoot entity, 
            IRepository repository)
        {
            _operations.Add(
                new Operation
                {
                    Entity = entity,
                    ProcessDate = DateTime.Now,
                    Repository = repository,
                    Type = TransactionType.Update
                });
        }

        /// <summary>
        /// Registers an <see cref="IEntity" /> instance to be removed through this <see cref="UnitOfWork" />.
        /// </summary>
        /// <param name="entity">The <see cref="IEntity" />.</param>
        /// <param name="repository">The <see cref="IUnitOfWorkRepository" /> participating in the transaction.</param>
        public void RegisterRemoved(
            IAggregateRoot entity, 
            IRepository repository)
        {
            _operations.Add(
                new Operation
                {
                    Entity = entity,
                    ProcessDate = DateTime.Now,
                    Repository = repository,
                    Type = TransactionType.Delete
                });
        }

        /// <summary>
        /// Commits all batched changes within the scope of a <see cref="TransactionScope" />.
        /// </summary>
        public void Commit()
        {
            using (var scope = new TransactionScope())
            {
                foreach (var operation in this._operations)
                {
                    try
                    {
                        switch (operation.Type)
                        {
                            case TransactionType.Insert:
                                operation.Repository.Add(operation.Entity);
                                break;
                            case TransactionType.Delete:
                                operation.Repository.Remove(operation.Entity);
                                break;
                            case TransactionType.Update:
                                operation.Repository.Change(operation.Entity);
                                break;
                        }
                    }
                    catch(Exception ex)
                    {
                        _operations.Clear();

                        // One of the operation failed causes UnitOfWork fail as a whole.                        
                        throw new UnitOfWorkException("One of the operations failed", ex);
                    }
                }

                // Commit the transaction
                scope.Complete();
            }

            // Clear everything
            _operations.Clear();
        }

        #endregion
    }
}
